﻿// Copyright (c) 2009, Heiko Wundram (modelnine@h8me.eu).
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
// conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
//   in the documentation and/or other materials provided with the distribution.
// * Neither the name of Heiko Wundram (h8me.eu) nor the names of its contributors may be used to endorse or promote products
//   derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;

namespace eu.h8me.Parsing.ParserLib {

    /// <summary>
    /// Helper class storing the actions associated with a parser state and a lookahead.
    /// </summary>
    internal class StateAction {

        // Internal sets of actions.
        private int? _Action;
        private readonly List<ReduceAction> _ReduceActions = new List<ReduceAction>();

        /// <summary>
        /// Initialize a new, empty set of state actions.
        /// </summary>
        public StateAction() {
            // No specific construction.
        }

        /// <summary>
        /// Initialize a state action with the specified shift or goto action.
        /// </summary>
        /// <param name="act">Target state for action.</param>
        public StateAction(int act) {
            // Initialize value for action.
            _Action = act;
        }

        /// <summary>
        /// Load a state action from a specified set of XML serialized node actions.
        /// </summary>
        /// <param name="xmlact">Action to bind to this state action.</param>
        /// <param name="symbids">Symbol IDs mapping.</param>
        /// <param name="prodids">Production IDs mapping.</param>
        public StateAction(XmlNode xmlact, Dictionary<string, Symbol> symbids, Dictionary<Symbol, Dictionary<int, int>> prodids) {
            // Check whether base action is set up.
            foreach( XmlAttribute attr in xmlact.Attributes )
                if( attr.Name == "action" )
                    // Load action (this is the action attribute).
                    _Action = Convert.ToInt32(attr.InnerText, CultureInfo.InvariantCulture);

            // Loop over reductions, attaching them.
            foreach( XmlNode xmlred in xmlact.SelectNodes("reduce") )
                _ReduceActions.Add(new ReduceAction(xmlred, symbids, prodids));
        }

        /// <summary>
        /// Load a state action from a passed stored state action object.
        /// </summary>
        /// <param name="act">State action to load.</param>
        /// <param name="symbids">Symbol IDs mapping.</param>
        /// <param name="prodids">Production IDs mapping.</param>
        public StateAction(StoredStateAction act, Dictionary<string, Symbol> symbids, Dictionary<Symbol, Dictionary<int, int>> prodids) {
            // Initialize data from stored set.
            _Action = act.Action;
            foreach( StoredReduceAction redact in act.ReduceActions )
                _ReduceActions.Add(new ReduceAction(redact, symbids, prodids));
        }

        /// <summary>
        /// Check whether this state has an action bound.
        /// </summary>
        public bool HasAction {
            get {
                // Return shift action state.
                return _Action.HasValue;
            }
        }

        /// <summary>
        /// Fetch the action bound to this state.
        /// </summary>
        public int Action {
            get {
                // Return shift action state.
                return _Action.Value;
            }
        }

        /// <summary>
        /// Return list of reduction actions bound to this state.
        /// </summary>
        public List<ReduceAction> ReduceActions {
            get {
                // Return list of reduce actions.
                return _ReduceActions;
            }
        }

        /// <summary>
        /// Store the current state action to the specified xml node, using elements from the passed document.
        /// </summary>
        /// <param name="doc">Document to use.</param>
        /// <param name="xmlact">Action node to "enhance".</param>
        public void StoreXML(XmlDocument doc, XmlNode xmlact) {
            // Set up flags.
            if( _Action.HasValue )
                xmlact.Attributes.Append(doc.CreateAttribute("action")).InnerText =
                    _Action.Value.ToString(CultureInfo.InvariantCulture);

            // Attach reduce actions.
            foreach( ReduceAction act in _ReduceActions )
                xmlact.AppendChild(act.StoreXML(doc));
        }

        /// <summary>
        /// Store the current state action to the specified output stream.
        /// </summary>
        /// <param name="os">Stream to write to.</param>
        public void StoreCS(StreamWriter os) {
            // Create state action object and store reduce actions.
            os.WriteLine("act = new StoredStateAction({0});", _Action.HasValue ? _Action.Value.ToString(CultureInfo.InvariantCulture) : "null");
            foreach( ReduceAction redact in _ReduceActions )
                redact.StoreCS(os);
        }

        /// <summary>
        /// Return a short string describing this state action.
        /// </summary>
        /// <returns>String form of the actions associated with this state.</returns>
        public override string ToString() {
            // Display action state.
            StringBuilder rv = new StringBuilder();
            if( _Action.HasValue )
                rv.AppendFormat("Act: {0}", _Action.Value);

            // Display reduce targets.
            if( _ReduceActions != null )
                foreach( ReduceAction act in _ReduceActions )
                    rv.AppendFormat(" Red: {0}", act);

            // Return string.
            return rv.ToString();
        }

        /// <summary>
        /// Resolve the conflicts that are present in this state action using the priority rules that are defined for the
        /// production and lookahead. This is only possible in case this is a simple Shift/Reduce conflict.
        /// </summary>
        /// <param name="symb">Lookahead symbol.</param>
        /// <param name="warnings">List of warning messages to note the (possible) conflict resolution to.</param>
        public void ResolveConflict(Symbol symb, List<string> warnings) {
            // Check whether this state qualifies for conflict resolution.
            if( _ReduceActions.Count > 1 ) {
                // Build list of productions present.
                StringBuilder prods = new StringBuilder();
                foreach( ReduceAction act in _ReduceActions )
                    prods.AppendFormat("{0}, ", act.ProductionRule);

                // Attach warning on productions.
                warnings.Add(String.Format("Can't resolve reduce/reduce conflict between productions {0}lookahead {1}", prods, symb));
                return;
            }

            // Check precedence of reduce action against shift.
            Production prod = _ReduceActions[0].ProductionRule;
            if( !prod.HasRulePrecedence || !symb.HasSymbolPrecedence ) {
                // Can't resolve when production has no precedence.
                warnings.Add(String.Format("Can't resolve conflict between {0} and {1}, production and/or symbol don't have precedence", prod, symb));
            } else if( prod.RulePrecedence > symb.SymbolPrecedence ) {
                // Reduce has higher priority, don't shift.
                _Action = null;
                warnings.Add(String.Format("Conflict between {0} and {1} resolved in favor of production (reduce, precedence)", prod, symb));
            } else if( prod.RulePrecedence < symb.SymbolPrecedence ) {
                // Shift has higher priority, don't reduce.
                _ReduceActions.Clear();
                warnings.Add(String.Format("Conflict between {0} and {1} resolved in favor of symbol (shift, precedence)", prod, symb));
            } else if( symb.HasSymbolAssociativity ) {
                // Both have equivalent precedence and associativity has been set up for lookahead token; decide based on that.
                switch( symb.SymbolAssociativity ) {

                    case Symbol.Associativity.Left:
                        // Left associative, means reduce.
                        _Action = null;
                        warnings.Add(String.Format("Conflict between {0} and {1} resolved in favor of production (reduce, left-assoc)", prod, symb));
                        break;

                    case Symbol.Associativity.Right:
                        // Right associative, means shift.
                        _ReduceActions.Clear();
                        warnings.Add(String.Format("Conflict between {0} and {1} resolved in favor of symbol (shift, right-assoc)", prod, symb));
                        break;

                    case Symbol.Associativity.NonAssoc:
                        // Non-associative, means error when tokens appear.
                        _Action = null;
                        _ReduceActions.Clear();
                        warnings.Add(String.Format("Conflict between {0} and {1} resolved (no action, non-assoc)", prod, symb));
                        break;

                }
            } else
                // Can't resolve.
                warnings.Add(String.Format("Can't resolve conflict between {0} and {1}, equal precedence, no associativity", prod, symb));
        }

    }

    /// <summary>
    /// Helper class storing a specific reduction action and the symbol and production taking part in it.
    /// </summary>
    internal class ReduceAction {

        // Data.
        private readonly Symbol _Symbol;
        private readonly int _Production;

        /// <summary>
        /// Initialize a new reduction action for the specified symbol and the specified production.
        /// </summary>
        /// <param name="symb">Symbol to reduce on.</param>
        /// <param name="prod">Production to use for reduction.</param>
        public ReduceAction(Symbol symb, int prod) {
            // Store data.
            _Symbol = symb;
            _Production = prod;
        }

        /// <summary>
        /// Initialize a new reduce action from the serialized state.
        /// </summary>
        /// <param name="xmlred">Reduction action as XML structure.</param>
        /// <param name="symbids">Symbol ID mapping.</param>
        /// <param name="prodids">Production ID mapping.</param>
        public ReduceAction(XmlNode xmlred, Dictionary<string, Symbol> symbids, Dictionary<Symbol, Dictionary<int, int>> prodids) {
            // Initialize state.
            _Symbol = symbids[xmlred.Attributes["symbol"].InnerText];
            _Production = prodids[_Symbol][Convert.ToInt32(xmlred.Attributes["production"].InnerText, CultureInfo.InvariantCulture)];
        }

        /// <summary>
        /// Initialize a new reduce action from the passed stored reduction state.
        /// </summary>
        /// <param name="redact">Reduce action to use.</param>
        /// <param name="symbids">Symbol ID mapping.</param>
        /// <param name="prodids">Production ID mapping.</param>
        public ReduceAction(StoredReduceAction redact, Dictionary<string, Symbol> symbids, Dictionary<Symbol, Dictionary<int, int>> prodids) {
            // Initialize state.
            _Symbol = symbids[redact.Symbol.Name];
            _Production = prodids[_Symbol][redact.Production];
        }

        /// <summary>
        /// Access the symbol that is being reduced to.
        /// </summary>
        public Symbol Symbol {
            get {
                // Symbol associated with reduce.
                return _Symbol;
            }
        }

        /// <summary>
        /// Access the production rule index that is being used for reduction.
        /// </summary>
        public int Production {
            get {
                // Return production.
                return _Production;
            }
        }

        /// <summary>
        /// Access the actual production rule that is being reduced.
        /// </summary>
        public Production ProductionRule {
            get {
                // Return production rule.
                return _Symbol[_Production];
            }
        }

        /// <summary>
        /// Serialize the current reduce action to the specified xml document, returning node for reduction.
        /// </summary>
        /// <param name="doc">Document to create nodes on.</param>
        /// <returns>Node that represents this reduce action.</returns>
        public XmlNode StoreXML(XmlDocument doc) {
            // Create node and attach attributes.
            XmlNode xmlred = doc.CreateElement("reduce");
            xmlred.Attributes.Append(doc.CreateAttribute("symbol")).InnerText = _Symbol.Name;
            xmlred.Attributes.Append(doc.CreateAttribute("production")).InnerText = _Production.ToString(CultureInfo.InvariantCulture);

            // Return node.
            return xmlred;
        }

        /// <summary>
        /// Store the current reduce action to the specified stream.
        /// </summary>
        /// <param name="os">Output stream to store to.</param>
        public void StoreCS(StreamWriter os) {
            // Attach to current action.
            os.WriteLine("act.Attach(new StoredReduceAction(symbs[\"{0}\"], {1}));", _Symbol.Name, _Production);
        }

        /// <summary>
        /// Return short string showing production action that is taken.
        /// </summary>
        /// <returns>String form of the reduction action.</returns>
        public override string ToString() {
            // Return rule info.
            return String.Format("{0} -> {1}", _Symbol, _Symbol[_Production]);
        }

    }

}
